package mmc.cell3;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import mmc.Constants;
import mmc.cell.Block;
import mmc.cell.CellPosition;

public class ServerChunkGenerator {
	
	private Map<HMPos, HeightMap> immediateHMs = new HashMap<HMPos, HeightMap>();
	private int iHits=0, iUses=0;
	private Map<HMPos, HeightMap> mergedHMs = new HashMap<HMPos, HeightMap>();
	private int mHits=0, mUses=0;
	static class HMPos{
		int x, z;
		public HMPos(int x, int z){
			this.x=x;
			this.z=z;
		}
		
		@Override
		public int hashCode(){
			return (x<<Integer.SIZE/2)^z;
		}
		
		@Override
		public boolean equals(Object o){
			HMPos o2 = (HMPos) o;
			return o2.x==x&&o2.z==z;
		}
	}
	public String dumpCacheEfficiency(){
		StringBuilder sb = new StringBuilder();
		
		sb.append(String.format("Immediate HMS %d hits, %d uses -> %.2f %%\n",iHits,iUses,100*iHits/(float)iUses));
		sb.append(String.format("Merged HMS %d hits, %d uses -> %.2f %%\n",mHits,mUses,100*mHits/(float)mUses));
		
		return sb.toString();
	}
	
	public ServerChunkGenerator(){
	}
	
	public void clearCache() {
		immediateHMs.clear();
		mergedHMs.clear();
	}
	
	public ServerChunk generateChunk(CellPosition pos){
		byte[][][] data = new byte[Constants.CHUNK_BLOCKS][Constants.CHUNK_BLOCKS][Constants.CHUNK_BLOCKS];
		/*
		if(pos.y<-Constants.CHUNK_BLOCKS){
			solid(data,(byte)3);
		}else if(pos.y<0){
			applyHM(data, pos);
		}else{
			solid(data,(byte)0);
		}*/
		
		applyHM(data, pos);
		
		return new ServerChunk(pos,data);
	}
	/*
	private void solid(byte[][][] data, byte value){
		for(int i=0;i<Constants.CHUNK_BLOCKS;i++){
			for(int j=0;j<Constants.CHUNK_BLOCKS;j++){
				for(int k=0;k<Constants.CHUNK_BLOCKS;k++){
					data[i][j][k]=value;
				}
			}
		}
	}*/
	
	private float noiseScale(float frequency){
		return (float)(2*Math.pow(Math.E,-frequency));
	}
	
	private float noiseAdd(float frequency){
		return 0.2f;
	}
	
	private HeightMap createImmediateHeightMap(CellPosition pos, int resolution){
		Random r = new Random(pos.calculateSeed());
		
		int subMapRes = resolution/2;
		HeightMap sm;
		
		if(subMapRes<=3)
			sm = new HeightMap(subMapRes, r);
		else
			sm = createImmediateHeightMap(pos, subMapRes);
		
		HeightMap ret = sm.stretch(resolution);
		
		HeightMap noise = new HeightMap(resolution, r);
		noise.scale((float)Math.pow(r.nextFloat(),2)*noiseScale(1f/resolution));
		noise.add(((float)Math.pow(r.nextFloat(),2)-0.5f)*noiseAdd(1f/resolution));
		ret.add(noise, 0, 0);
		
		return ret;
	}
	
	public HeightMap getImmediateHeightMap(CellPosition pos, int resolution){
		HMPos key = new HMPos(pos.x,pos.z);
		
		HeightMap ret = immediateHMs.get(key);
		iUses++;
		
		if(ret==null){
			ret = createImmediateHeightMap(pos, resolution);
			immediateHMs.put(key, ret);
		}else{
			iHits++;
		}
		
		return ret;
	}
	
	private HeightMap createMergedHeightMap(CellPosition pos){
		int resolution = 16;//Constants.CHUNK_BLOCKS/8;
		int overlap = 5;
		HeightMap roughMerged = new HeightMap(3*resolution+2*overlap);
		
		for(int i=-1;i<2;i++){
			for(int j=-1;j<2;j++){
				roughMerged.add(getImmediateHeightMap(new CellPosition(pos.x+i*Constants.CHUNK_BLOCKS,pos.y,pos.z+j*Constants.CHUNK_BLOCKS), resolution+2*overlap), 
						(i+1)*resolution, (j+1)*resolution);
			}
		}
		
		roughMerged.diffuse(4);
		
		HeightMap ret = roughMerged.extractCenter(Constants.CHUNK_BLOCKS, resolution+overlap); 
		
		ret.diffuse(4);
		
		ret.scale(0.5f);
		ret.add(0.5f);
		
		return ret;
	}
	
	public HeightMap getMergedHeightMap(CellPosition pos){
		HMPos key = new HMPos(pos.x,pos.z);
		
		HeightMap ret = mergedHMs.get(key);
		mUses++;
		
		if(ret==null){
			ret = createMergedHeightMap(new CellPosition(pos.x,-Constants.CHUNK_BLOCKS,pos.z));
			mergedHMs.put(key, ret);
		}else{
			mHits++;
		}
		
		return ret;
	}
	
	public void applyHM(byte[][][] data, CellPosition pos){
		HeightMap hm = getMergedHeightMap(pos);
		
		for(int i=0;i<Constants.CHUNK_BLOCKS;i++){
			for(int j=0;j<Constants.CHUNK_BLOCKS;j++){
				for(int k=0;k<Constants.CHUNK_BLOCKS;k++){
					float v = hm.data[i][k] - pos.y/(float)Constants.CHUNK_BLOCKS - 1;
					
					if(j == (int)Math.floor(v*(float)Constants.CHUNK_BLOCKS)){
						data[i][j][k] = Block.GRASS;
					}else if((j+20)/(float)Constants.CHUNK_BLOCKS < v){
						data[i][j][k] = Block.STONE;
					}else if(j/(float)Constants.CHUNK_BLOCKS < v){
						data[i][j][k] = Block.EARTH;
					}else{
						data[i][j][k] = Block.AIR_SUN;
					}
				}
			}
		}
	}
}

